# coding=utf-8
"""
author = jamon
"""

import asyncio
import struct
from typing import Optional

import ujson

from obespoir.base.global_object import GlobalObject
from obespoir.share.espoir_log import EspoirLog, global_log


class DataException(Exception):
    pass


class ObProtocol(asyncio.Protocol, EspoirLog):
    """消息协议，包含消息处理"""

    def __init__(self):
        # (int, int, int)  -> (message_length, command_id, version)
        super().__init__()
        self.hand_fmt = "iii"
        self.head_len = struct.calcsize(self.hand_fmt)
        self.encode_ins = None
        self.version = 0

        self._buffer = b""  # 数据缓冲buffer
        self._head = None  # 消息头

    def pack(self, data, command_id: int, session_id: str = "", to=None, **kwargs):
        """
        打包消息， 用於傳輸
        :param to:
        :param session_id:
        :param data:  傳輸數據
        :param command_id:  消息ID
        :return:
        """
        to_pack_data = {"session_id": session_id, "src": GlobalObject().id,
                        "to": to, "data": data, "prev": GlobalObject().type}
        to_pack_data.update(kwargs)
        data = ujson.dumps(to_pack_data)
        data = self.encode_ins.encode(data)
        length = data.__len__() + self.head_len
        head = struct.pack(self.hand_fmt, length, command_id, self.version)
        return head + data

    def process_data(self, data):
        if isinstance(data, str):
            data = bytes(data, encoding='utf8')
        self._buffer += data
        _buffer = b""
        if self._head is None:  # noqa
            if len(self._buffer) < self.head_len:
                return

            self._head = struct.unpack(self.hand_fmt,
                                       self._buffer[:self.head_len])  # 包头
            self._buffer = self._buffer[self.head_len:]
        content_len = self._head[0] - self.head_len
        if len(self._buffer) >= content_len:
            data = self.encode_ins.decode(self._buffer[:content_len])  # 解密
            if not data:
                raise DataException()
            global_log.debug("receive a message data",
                             raw_data=self._buffer[:200])
            asyncio.ensure_future(self.message_handle(
                self._head[1], self._head[2], data), loop=GlobalObject().loop)

            _buffer = self._buffer[content_len:]
            self._buffer = b""
            self._head = None
        # globalLog.info("process_data: {}".format([len(_buffer),
        # len(self._buffer), self._buffer[:100]]))
        return _buffer

    async def message_handle(self, command_id: int, version, data):
        """
        实际处理消息
        :param command_id:
        :param version:
        :param data:
        :return:
        """
        raise NotImplementedError

    def connection_made(self, transport):
        super(ObProtocol, self).connection_made(transport)

    def data_received(self, data: bytes):
        while data:  # 解决TCP粘包问题
            data = self.process_data(data)

    def eof_received(self):
        raise NotImplementedError

    def connection_lost(self, error: Optional[Exception]):
        super(ObProtocol, self).connection_lost(error)
